"""
请使用Python3.9及以上版本运行
因为程序中使用了 | 运算符来合并字典
(或者你可以改写这部分代码，位置: CONST_OBJECT.__or__)
"""
import random
import analyse


class CONST_OBJECT:
    """
    常量
    """
    def __init__(self, v, _type):
        if not isinstance(v, dict):
            self._value = {_type: v}
        else:
            self._value = v

    @property
    def value(self):
        return self._value

    def __or__(self, other):
        return CONST_OBJECT(self.value | other.value, '_')

    def __contains__(self, item):
        for _key in item.value.keys():
            _value = item.value[_key]
            if _key in self._value.keys():
                if self._value[_key] == _value:
                    pass        # 可以
                else:
                    return False
            else:
                return False
        return True

    def __eq__(self, other):
        return True if self._value == other.value else False

    def __repr__(self):
        return str(self.value)


class Length(CONST_OBJECT):
    def __init__(self, v):
        super().__init__(v, "length")


class Key(CONST_OBJECT):
    def __init__(self, v):
        super().__init__(v, "key")


class File(CONST_OBJECT):
    """
    mode : 使用 osu / malody 铺
    """
    def __init__(self, path):
        super().__init__(path, "osu/malody")


LEFT = CONST_OBJECT(0, "direction")
RIGHT = CONST_OBJECT(1, "direction")
RANDOM = CONST_OBJECT(2, "direction")          # 随机生成方向
BITE = CONST_OBJECT(3, "direction")            # 首尾相接

LOUTI = CONST_OBJECT(0, "mode")
JIAOHU = CONST_OBJECT(10, "mode")
ZONGLIAN = CONST_OBJECT(20, "mode")
MESS = CONST_OBJECT(30, "mode")           # 随机
MIXED = CONST_OBJECT(40, "mode")


KEY = Key(4)
# ZONGLIANLIMIT = CONST_OBJECT(5, "direction")


def getKeyboardMap():
    # 获取键位映射表
    if KEY.value["key"] == 4:
        return 'dfjk'
    elif KEY.value["key"] == 5:
        return 'dfhjk'
    elif KEY.value["key"] == 6:
        return 'dfghjk'
    elif KEY.value["key"] == 7:
        return 'dfghjkl'
    ...


def getLouti(code):
    """
    获取楼梯对应的beatmap
    :param code: 存储了 length / direction/ ...
    :return:
    """
    length = code.value["length"]
    key = code.value["key"]
    beatmap = []
    rightLouti = list(range(key))
    leftLouti = list(reversed(rightLouti))

    startKEY = 0
    # startKEY = random.randint(0, key-1)
    length += startKEY
    # direction = direction.value["direction"]
    if RIGHT in code:
        beatmap += rightLouti * (length // key)
        beatmap += rightLouti[:length - key * (length // key)]

    if LEFT in code:      # LEFT / RIGHT
        beatmap += leftLouti * (length // key)
        beatmap += leftLouti[:length - key * (length // key)]

    elif RANDOM in code:
        _lastDirection = random.randint(0, 1)
        _direction = _lastDirection
        while length >= key:
            if _direction == _lastDirection:      # 方向相同，楼梯直接叠加
                beatmap += (rightLouti if _direction else leftLouti)
                length -= key
            else:
                beatmap.pop()
                beatmap += (rightLouti if _direction else leftLouti)
                length -= key - 1
            _lastDirection = _direction
            _direction = random.randint(0, 1)
        if length:
            _direction = random.randint(0, 1)
            if _direction == _lastDirection:  # 方向相同，楼梯直接叠加
                beatmap += (rightLouti if _direction else leftLouti)[:length - key * (length // key)]
            else:
                beatmap.pop()
                beatmap += (rightLouti if _direction else leftLouti)[:length - key * (length // key) + 1]
    else:   # BITE
        _direction = random.randint(0, 1)       # 起始方向
        beatmap = [-1]
        while length >= key:
            beatmap.pop()
            beatmap += (rightLouti if _direction else leftLouti)
            _direction = 1 - _direction
            length -= key - 1

        if length:
            beatmap.pop()
            beatmap += (rightLouti if _direction else leftLouti)[:length - key * (length // key)]

    return beatmap[startKEY:]


def getJiaohu(code):
    """
    获取交互
    :param key:
    :param code:
    :return:
    """
    length = code.value["length"]
    key = code.value["key"]
    beatmap = []
    startKey = random.randint(0, key - 1)
    endKey = random.randint(0, key - 1)
    while endKey == startKey:               # 起始点不能与结束点重合
        endKey = random.randint(0, key - 1)
    singleJiaohu = [startKey, endKey]
    beatmap += singleJiaohu * (length // 2)
    beatmap += singleJiaohu[:length - 2 * (length // 2)]

    return beatmap


def getZonglian(code):
    length = code.value["length"]
    key = code.value["key"]
    beatmap = []
    startKey = random.randint(0, key - 1)
    beatmap += [startKey] * length

    return beatmap


def getMess(code):
    length = code.value["length"]
    key = code.value["key"]
    beatmap = []
    for _ in range(length):
        beatmap.append(random.randint(0, key - 1))
    return beatmap


def _getBeatmap(code):
    """
    获取一种模式下的铺面
    :param code: 模式代码
        <!> 方向的限制对 JIAOHU / MESS / LOUTI 无效
    :return:
    """
    mode = code.value["mode"]
    length = code.value["length"]
    key = code.value["key"]
    # print(mode, direction)
    if LOUTI in code:
        return getLouti(code)
    elif JIAOHU in code:
        return getJiaohu(code)
    elif ZONGLIAN in code:
        return getZonglian(code)
    elif MESS in code:
        return getMess(code)
    else:
        # 种类数量随机
        # 每个种类物量随机
        modeNum = random.randint(5, 7)
        modeList = [random.choice([LOUTI, JIAOHU, MESS, ZONGLIAN]) for _ in range(modeNum)]
        modeLength = [0] * modeNum
        beatmap = []

        def check(_modeLength, _modeList):
            # 校验随机生成的结果是否合法
            if 0 in _modeLength:
                return False
            for i in range(len(_modeList)):
                _mode = _modeList[i]
                _length = _modeLength[i]
                if _mode == LOUTI:
                    if _length < key:
                        return False
                elif _mode == JIAOHU:
                    if _length < 2:
                        return False
                # elif _mode == ZONGLIAN:
                #     if _length > 6:
                #         return False
            return True

        while not check(modeLength, modeList):
            modePercent = [random.random() for _ in range(modeNum)]
            modeLength = [int(percent / sum(modePercent) * length) for percent in modePercent]
            minIndex = modeLength.index(min(modeLength))
            modeLength[minIndex] += length - sum(modeLength)
        index = 0
        for mode in modeList:
            beatmap += _getBeatmap(mode | RANDOM | Length(modeLength[index]) | KEY)
            index += 1
        return beatmap


def getBeatmap(codeList):
    # osu / malody 模式下，其余模式无效
    index = 0
    for code in codeList:
        if "osu/malody" in code.value:
            return analyse.file2map(code.value["osu/malody"])
        codeList[index] = code | LENGTH | KEY
        index += 1
    if len(codeList) > 1:
        newMap = _getBeatmap(codeList[0])
        for i in range(1, len(codeList)):
            newMap = mergeBeatmap(
                newMap, _getBeatmap(codeList[i]), codeList[i].value["key"]
            )
        return newMap
    else:
        _newMap = _getBeatmap(codeList[0])
        newMap = [[0] * KEY.value["key"] for i in range(codeList[0].value['length'])]
        for i in range(codeList[0].value['length']):
            newMap[i][_newMap[i]] = 1

        return newMap


def mergeBeatmap(map1, map2, key):
    newMap = []
    if isinstance(map1[0], int) and isinstance(map2[0], int):   # 两个简单铺面合并
        for i in range(len(map1)):
            newMap.append([0]*key)
            newMap[-1][map1[i]] = 1
            if not newMap[-1][map2[i]]:     # 目标位置经过map1覆盖后仍为空白
                k = random.random()
                if k <= ALPHA:                  # 覆盖率为ALPHA
                    newMap[-1][map2[i]] = 1
            # 不需要else，如果此处不为空白，则再覆盖无意义
        return newMap
    elif isinstance(map1[0], list) and isinstance(map2[0], list):   # 两个复杂铺面合并
        for i in range(len(map1)):
            newMap.append([0]*key)
            for j in range(len(map1[i])):
                newMap[-1][j] = map1[i][j]
            for j in range(len(map2[i])):
                if map2[i][j]:
                    k = random.random()
                    if k <= ALPHA:  # 覆盖率为ALPHA
                        newMap[-1][j] = 1
                # if map2[i][j]:           # 保留原始代码
                #     newMap[-1][j] = 1
        return newMap
    else:       # 复杂铺面与简单铺面合并
        # 本质上是把简单铺面转化成复杂铺面，再调用本方法的复杂铺面与复杂铺面合并功能
        # 因此无需编写覆盖率算法
        if isinstance(map1[0], list):       # map2是简单铺面
            # map2 = mergeBeatmap(map2, [0]*len(map2))   # 将map2转化成复杂铺面
            _map2 = []
            for i in range(len(map2)):
                _map2.append([0] * key)
                _map2[-1][map2[i]] = 1
            return mergeBeatmap(map1, _map2, key)

        else:       # map1是简单铺面
            _map1 = []
            for i in range(len(map1)):
                _map1.append([0] * key)
                _map1[-1][map2[i]] = 1
            return mergeBeatmap(_map1, map2, key)


def checkBoot():
    # 启动游戏前检查参数合法性
    print(f"""checking game parameters...
CODE : {CODE}
REVERSE: {REVERSE}
ALPHA: {ALPHA}""")
    if len(CODE) < 1:
        return 0
    else:
        return 1

# def init():
#     global CODE, lastTarget, beatmap, combo, accList, effectList, slideList, score, REVERSE, LENGTH, LIMIT, ALPHA
#     CODE = []  # 决定了生成的铺面种类
#     lastTarget = 0  # 点击目标会使该变量 自减1, 当该变量值小于 LIMIT 时，会调用getBeatmap生成新铺面
#     beatmap = []  # 铺面
#     combo = 0  # 连击
#     accList = [1]
#     effectList = []
#     slideList = []
#     score = 0  # 命中数
#     REVERSE = True
#     LENGTH = KEY.value["key"] * 10  # 每次生成的铺面长度
#     LIMIT = LENGTH - 1
#     ALPHA = 0.33  # mergeBeatmap 覆盖率

CODE = []  # 决定了生成的铺面种类
lastTarget = 0  # 点击目标会使该变量 自减1, 当该变量值小于 LIMIT 时，会调用getBeatmap生成新铺面
beatmap = []  # 铺面
combo = 0  # 连击
accList = [1]
effectList = []
slideList = []
score = 0  # 命中数
REVERSE = False
LENGTH = Length(KEY.value["key"] * 10)  # 每次生成的铺面长度
LIMIT = KEY.value["key"] * 10
ALPHA = 0.33        # mergeBeatmap 覆盖率
TIMER = True        # 启用计时


if __name__ == '__main__':
    # test getLouti
    print(getBeatmap([File("1.osu")]))
    print(getBeatmap([Length(40) | LEFT | KEY | LOUTI]))
    print(getBeatmap([Length(40) | LEFT | KEY | JIAOHU]))
    print(getBeatmap([Length(40) | LEFT | KEY | ZONGLIAN]))
    print(getBeatmap([Length(40) | LEFT | KEY | MESS]))
    print(getBeatmap([Length(40) | LEFT | KEY | MIXED]))
    # print(getLouti(15, BITE, key_))
    #
    # # test getJiaohu
    # print(getJiaohu(8, key_))
    # print(getJiaohu(15, key_))
    #
    # # test getZonglian
    # print(getZonglian(8, key_))
    # print(getZonglian(15, key_))
    #
    # # test getMess
    # print(getMess(8, key_))
    # print(getMess(15, key_))
    #
    # # test getBeatmap
    # print(getBeatmap(JIAOHU, 8, key_))
    # print(getBeatmap(LOUTI | LEFT, 15, key_))
    #
    # # test mergeBeatmap
    # a = getZonglian(8, key_)
    # b = getLouti(8, LEFT, key_)
    # c = getMess(8, key_)
    # d = getJiaohu(8, key_)
    # print(a)
    # print(b)
    # print(c)
    # print(d)
    # print(mergeBeatmap(a, b, key_))
    # print(mergeBeatmap(c, d, key_))
    # print(mergeBeatmap(mergeBeatmap(a, b, key_), c, key_))
    # print(mergeBeatmap(mergeBeatmap(a, b, key_), mergeBeatmap(c, d, key_), key_))
    #
    # for idx in range(10000):
    #     sys.stdout.write("\r process: %d / %d" % (idx+1, 10000))
    #     if not getBeatmap(MIXED, 40, key_):
    #         raise ValueError(
    #             "wrong data"
    #         )
